package com.k.kc.util.json.jackson;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Objects;
import java.util.TimeZone;

/**
 * @author ljw
 */
public class JacksonUtils {
    public static final String YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    public static final String YYYY_MM_DD = "yyyy-MM-dd";
    public static final String HH_MM_SS = "HH:mm:ss";

    public static final String TIMEZONE_BEIJING = "Asia/Shanghai";
    public static final String TIMEZONE_UTC = "GMT";
    public static final String JSON_DATE_FORMAT = YYYY_MM_DD_HH_MM_SS;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        //序列化日期格式
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS)));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(YYYY_MM_DD)));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(HH_MM_SS)));
        javaTimeModule.addSerializer(Date.class, new CustomDateSerializer(YYYY_MM_DD_HH_MM_SS, TIMEZONE_BEIJING));

        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS)));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(YYYY_MM_DD)));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(HH_MM_SS)));
        javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer(YYYY_MM_DD_HH_MM_SS, TIMEZONE_BEIJING));
        MAPPER.registerModule(javaTimeModule);
    }

    public static void configMapper(ObjectMapper mapper, String jsonDateFormat, String zoneId) {
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        //序列化日期格式
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(jsonDateFormat)));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(YYYY_MM_DD)));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(HH_MM_SS)));
        javaTimeModule.addSerializer(Date.class, new CustomDateSerializer(jsonDateFormat, zoneId));

        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(jsonDateFormat)));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(YYYY_MM_DD)));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(HH_MM_SS)));
        javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer(jsonDateFormat, zoneId));
        mapper.registerModule(javaTimeModule);
    }

    public static ObjectMapper getMapper() {
        return MAPPER;
    }

    public static ObjectMapper getMapper(String jsonDateFormat, String zoneId) {
        ObjectMapper om = new ObjectMapper();
        configMapper(om, jsonDateFormat, zoneId);
        return om;
    }

    public static ObjectMapper getMapper(String zoneId) {
        return getMapper(JSON_DATE_FORMAT, zoneId);
    }

    public static String toJson(Object obj) throws JsonProcessingException {
        return getMapper().writeValueAsString(obj);

    }

    public static <T> T toObj(String jsonStr, Class<T> cls) throws JsonProcessingException {
        return getMapper().readValue(jsonStr, cls);
    }

    public static <T> T toObj(String jsonStr, TypeReference<T> typeReference) throws JsonProcessingException {
        return getMapper().readValue(jsonStr, typeReference);
    }

    static class CustomDateSerializer extends JsonSerializer<Date> {
        private static final String defaultZoneId = "Asia/Shanghai";
        private final DateFormat dateFormat;
        private final String zoneId;

        public CustomDateSerializer() {
            this(YYYY_MM_DD_HH_MM_SS);
        }

        public CustomDateSerializer(String pattern) {
            this.dateFormat = new SimpleDateFormat(pattern);
            this.zoneId = defaultZoneId;
        }

        public CustomDateSerializer(String pattern, String zoneId) {
            this.dateFormat = new SimpleDateFormat(pattern);
            this.zoneId = zoneId;
        }

        @Override
        public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            TimeZone timeZone = TimeZone.getTimeZone(zoneId);
            dateFormat.setTimeZone(timeZone);
            String formattedDate = dateFormat.format(value);
            gen.writeString(formattedDate);
        }

        /**
         * Date 类型走的这个方法，所以要重写此方法。
         *
         * @param value       value
         * @param gen         gen
         * @param serializers SerializerProvider
         * @param typeSer     typeSer
         * @throws IOException e
         */
        @Override
        public void serializeWithType(Date value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
            serialize(value, gen, serializers);
        }
    }

    static class CustomDateDeserializer extends StdDeserializer<Date> {
        private static final String defaultZoneId = "Asia/Shanghai";
        private final DateFormat dateFormat;
        private final String zoneId;

        public CustomDateDeserializer() {
            this(YYYY_MM_DD_HH_MM_SS);
        }

        public CustomDateDeserializer(String pattern) {
            super(Date.class);
            this.dateFormat = new SimpleDateFormat(pattern);
            this.zoneId = defaultZoneId;
        }

        public CustomDateDeserializer(String pattern, String zoneId) {
            super(Date.class);
            this.dateFormat = new SimpleDateFormat(pattern);
            this.zoneId = zoneId;
        }

        @Override
        public Date deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
            Date date = null;
            Exception ex = null;
            String dateString = p.getText();
            TimeZone timeZone = TimeZone.getTimeZone(zoneId);
            try {
                dateFormat.setTimeZone(timeZone);
                date = dateFormat.parse(dateString);
            } catch (Exception e) {
                // handler long date
                ex = e;
            }
            try {
                date = deserializeDateLong(dateString);
            } catch (Exception e) {
                ex = e;
            }
            if (Objects.isNull(date)) {
                throw new IOException("Failed to parse date: " + dateString, ex);
            } else {
                return date;
            }
        }

        private Date deserializeDateLong(String dateString) {
            return new Date(Long.parseLong(dateString));
        }

        /**
         * Date 类型走的这个方法所以要重写此方法
         *
         * @param p                parser
         * @param ctx              ctx
         * @param typeDeserializer type
         * @return Object
         * @throws IOException e
         */
        @Override
        public Object deserializeWithType(JsonParser p, DeserializationContext ctx, TypeDeserializer typeDeserializer) throws IOException {
            return deserialize(p, ctx);
        }
    }
}
